#ifndef _MATRIX_H_
#define MATRIX_H
#include <memory.h>

/* Esta clase permite trabajar con matrices de 2x2 3x3 y 4x4 unicamente */
class Matrix
{
private:
	static int i;
	static Matrix * class_matrix[3];
	// Esto esta para hacer mas rapida la identidad y la inicializacion.
	static float * identity_array_2x2;

	// Esto esta para hacer mas rapida la identidad y la inicializacion.
	static float * identity_array_3x3;

	// Esto esta para hacer mas rapida la identidad y la inicializacion.
	static float * identity_array_4x4;
	// - - - - - - - - - - - - - - - Class attributes - - - - - - - - - - - - -
public: 

	// Returns identity matrix. El valor retornado es una nueva matriz con el resultado de la operacion
	static Matrix & identity(int dimension);

	// Inicializar solo una vez o van a tener memory leacks 
	static void initalize();

	// Luego de llamar esta funcion no realizar operaciones sobre matrices
	static void finalize();

	// - - - - - - - - - - - - - Instance attributes - - - - - - - - - - - - - - 
private:
		// Cantidad de valores del array de numeros que componen la matriz
	const int values;

	// Rango de la matriz
	const int dimension;

	// Referencia a la matriz que le corresponde para realizar las operaciones entre pares de matrices.
	Matrix * M;

public: 

	// Matrix values. El valor retornado es una nueva matriz con el resultado de la operacion
	float * m;

	// Multiplicacion de matrices. El valor retornado es una nueva matriz con el resultado de la operacion
	Matrix & operator*(Matrix & matrix)
	{
		for (i = 4; --i>=0;)
			M->m[i] = this->m[i] * matrix.m[i];
		return *M;
	}

	// Suma matrices. El valor retornado es una nueva matriz con el resultado de la operacion
	Matrix & operator+(Matrix & matrix)
	{
		for (i = 4; --i>=0;)
			M->m[i] = this->m[i] + matrix.m[i];
		return *M;
	}

	// Resta matrices. El valor retornado es una nueva matriz con el resultado de la operacion
	Matrix & operator-(Matrix & matrix)
	{
		for (i = 4; --i>=0;)
			M->m[i] = this->m[i] - matrix.m[i];
		return *M;
	}

	// Returns this transposed matrix
	Matrix & operator!()
	{
		for (int i = 0; i<dimension; i++)
		{
			int r = dimension - i;
			for (int j = 1, h = i*(dimension+1); j<r; j++)
			{
				static float aux = M->m[j + h];
				M->m[j + h] = M->m[h + j*dimension];
				M->m[h + j*dimension] = aux;
			}
		}
		return *M;
	}

	// Multiply matrix
	Matrix & operator*=(Matrix & matrix)
	{
		for (i = 4; --i>=0;)
			m[i] *= matrix.m[i];
		return *this;
	}

	// Multiply matrix per escalar
	Matrix & Matrix::operator*=(float & val)
	{
		for (i = 4; --i>=0;)
			m[i] *= val;
		return *this;
	}

	// Adds a matrix to this one
	Matrix & operator+=(Matrix & matrix)
	{
		for (i = 4; --i>=0;)
			m[i] += matrix.m[i];
		return *this;
	}

	// Le resta una matriz a esta
	Matrix & operator-=(Matrix & matrix)
	{
		for (i = 4; --i>=0;)
			m[i] -= matrix.m[i];
		return *this;
	}

	Matrix(int dim) : values(dim * dim), dimension(dim)
	{
		m = new float[values];
		memset(m, 0, values * sizeof(float));
		M = 0;
		if (dimension == 2 || dimension == 3 || dimension == 4 )
			M = class_matrix[dimension - 2];
		// else Tendria que dar alguna manera de informar que esta matriz es invalida.
	}

	~Matrix() { M = 0; }
};

#endif // _MATRIX_H_
